#include "common.h"
#include "ftp.h"
#include <pthread.h>

static void scan_submitted(int fd_submitted,int fd_sites);
static void scan_forRange(char *ip_from,char *ip_to,int fd_sites,int nthread);
static int try_login(char *ip_addr,char *port,char *username,char *password);

static void *scan_thread(void *arg);
struct scan_thread_arg {
	int fd_sites;
	unsigned int from_addr;
	unsigned int naddr;
};
static pthread_mutex_t scan_mutex;
static int scanned_count;

/**
 * scan the specified hosts,
 * try to find out which of them host ftp server.
 * this program only scan the 21 port,and try to login as USER anonymous
 * if login succeeded,write one record to the argv[2] file.
 *
 * @argv[1] sites_submitted file
 * @argv[2] sites file
 * @argv[3] ip address from,e.g. "192.168.0.0"
 * @argv[4] ip address to,e.g. "192.168.255.255"
 * @argv[5] number of threads
 */

int main(int argc,char *argv[]) {
	
	int fd_submitted;
	int fd_sites;
	int ret;
	int nthread;

	if(argc != 6) {
		MSG("argc error");
		exit(EXIT_FAILURE);
	}

	nthread = atoi(argv[5]);
	if(nthread == 0) {
		MSG("number of thread error");
		exit(EXIT_FAILURE);
	}

	if(signal(SIGPIPE,sig_pipe) == SIG_ERR) {
		MSG("signal failed");
		exit(EXIT_FAILURE);
	}

	fd_submitted = open(argv[1],O_RDONLY);
	if(fd_submitted == -1) {
		MSG("open %s failed",argv[1]);
		exit(EXIT_FAILURE);
	}

	fd_sites = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0644);
	if(fd_sites == -1) {
		MSG("open %s failed",argv[2]);
		exit(EXIT_FAILURE);
	}
	
	scan_submitted(fd_submitted,fd_sites);
	ret = close(fd_submitted);
	if(ret == -1) {
		MSG("close sites_submitted failed");
	}

	scan_forRange(argv[3],argv[4],fd_sites,nthread);
	ret = close(fd_sites);
	if(ret == -1) {
		MSG("close sites failed");
	}

	run_log("scan finished");

	exit(EXIT_SUCCESS);
}

static void scan_submitted(int fd_submitted,int fd_sites) {

	char ip_addr[MAXNAME];
	char port[MAXNAME];
	char username[MAXNAME];
	char password[MAXNAME];
	char buf[MAXLINE];
	int ret;
	FILE *fp_submitted;

	fp_submitted = fdopen(fd_submitted,"r");

	while(1) {
		if(fgets(ip_addr,MAXNAME,fp_submitted) == NULL) {
			MSG("scan submitted done");
			break;
		}
		if(fgets(port,MAXNAME,fp_submitted) == NULL) {
			MSG("unexpected EOF of sites_submitted:ip_addr:%s",ip_addr);
			break;
		}
		if(fgets(username,MAXNAME,fp_submitted) == NULL) {
			MSG("unexpected EOF of sites_submitted:ip_addr:%s",ip_addr);
			break;
		}
		if(fgets(password,MAXNAME,fp_submitted) == NULL) {
			MSG("unexpected EOF of sites_submitted:ip_addr:%s",ip_addr);
			break;
		}
		if(fgets(buf,MAXLINE,fp_submitted) == NULL) {
			MSG("unexpected EOF of sites_submitted");
			break;
		}

		
		if(ip_addr[strlen(ip_addr)-1] == '\n')
			ip_addr[strlen(ip_addr)-1] = 0;

		if(port[strlen(port)-1] == '\n')
			port[strlen(port)-1] = 0;

		if(username[strlen(username)-1] == '\n')
			username[strlen(username)-1] = 0;
		
		if(password[strlen(password)-1] == '\n')
			password[strlen(password)-1] = 0;

		ret = try_login(ip_addr,port,username,password);
		if(ret == 0) {
			snprintf(buf,MAXLINE,"%s\t%s\t%s\t%s\n",ip_addr,port,
								  username,password);
			ret = writen(fd_sites,buf,strlen(buf));
			if(ret == -1) {
				MSG("writen failed:(%s %s to sites)",ip_addr,port);
				exit(EXIT_FAILURE);
			}
		}
	}
	run_log("scan_submitted finished");
}

static void scan_forRange(char *ip_from,char *ip_to,int fd_sites,int nthread) {
	
	struct in_addr from_addr,to_addr;
	unsigned int naddr;
	int ret;
	pthread_t ths[nthread];
	struct scan_thread_arg arguments[nthread];
	unsigned int num_per_thread;
	unsigned int num_mode;
	unsigned int from_tmp;
	int i;
	pthread_attr_t attr;

	ret = inet_pton(AF_INET,ip_from,&from_addr);
	if(ret <= 0) {
		MSG("ip_from inet_pton failed:%s",ip_from);
		return;
	}

	ret = inet_pton(AF_INET,ip_to,&to_addr);
	if(ret <= 0) {
		MSG("ip_to inet_pton failed:%s",ip_to);
		return;
	}

	if(ntohl(from_addr.s_addr) > ntohl(to_addr.s_addr)) {
		MSG("addr range error:%s - %s",ip_from,ip_to);
		return;
	}

	naddr = ntohl(to_addr.s_addr) - ntohl(from_addr.s_addr) + 1;
	num_per_thread  = naddr / nthread;
	num_mode = naddr % nthread;
	from_tmp = ntohl(from_addr.s_addr);

	for(i = 0; i < nthread; i++) {
		arguments[i].fd_sites = fd_sites;

		arguments[i].from_addr = from_tmp;
		arguments[i].naddr = (i < (int)num_mode) ? (num_per_thread+1) : num_per_thread;
		from_tmp += (i < (int)num_mode) ? (num_per_thread+1) : num_per_thread;
	}

	ret = pthread_mutex_init(&scan_mutex,NULL);
	if(ret != 0) {
		MSG("pthread_mutex_init failed:scan_mutex");
		return;
	}

	ret = pthread_attr_init(&attr);
	if(ret != 0) {
		MSG("pthread_attr_init failed");
		return;
	}

	pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN*10);

	for(i = 0; i < nthread; i++) {
		ret = pthread_create(&ths[i],&attr,scan_thread,(void *)&arguments[i]);
		if(ret != 0) {
			MSG("pthread_create failed:scan_thread");
			return;
		}
	}
	
	for(i = 0; i < nthread; i++) {
		ret = pthread_join(ths[i],NULL);
		if(ret != 0) {
			MSG("pthread_join failed:scan_thread")
			return;
		}
	}

	ret = pthread_attr_destroy(&attr);
	if(ret != 0) {
		MSG("pthread_attr_destroy failed");
		return;
	}

	ret = pthread_mutex_destroy(&scan_mutex);
	if(ret != 0) {
		MSG("pthread_mutex_destroy failed:scan_mutex");
		return;
	}
	run_log("scan_forRange finished");
}

/*
static int try_login(char *ip_addr,char *port,char *username,char *password) {
	return try_login_timeout(ip_addr,port,username,password,0);
}
*/

static int try_login(char *ip_addr,char *port,char *username,char *password) {
	
	int sockfd;
	struct sockaddr_in servaddr;
	short int port_num;
	int ret;
	char sendbuf[MAXLINE];

	run_log("try to login - %s %s %s %s",ip_addr,port,username,password);
	
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1) {
		MSG("socket failed:scan control socket");
		run_log("login failed - %s %s %s %s",ip_addr,port,username,password);
		return -1;
	}

	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	port_num = atoi(port);
	if(port_num == 0) {
		MSG("invalid port number:%s",port);
		goto out;
	}
	servaddr.sin_port = htons(port_num);
	ret = inet_pton(AF_INET,ip_addr,&servaddr.sin_addr);

	ret = connect_timeout(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	if(ret == -1) {
		MSG("connect failed:%s",ip_addr);
		goto out;
	}
	
	ret = res_from_server(sockfd,NULL,0);
	if(ret == -1) {
		MSG("res_from_server failed");
		goto out;
	}
	else if(ret != 220) {
		MSG("no 220 returned");
		goto out;
	}

	snprintf(sendbuf,MAXLINE,"%s %s\r\n",CLI_USER,username);
	ret = cmd_to_server(sockfd,sendbuf);
	if(ret == -1) {
		MSG("cmd_to_server failed");
		goto out;
	}

	ret = res_from_server(sockfd,NULL,0);
	if(ret == -1) {
		MSG("res_from_server failed");
		goto out;
	}
	else if(ret != 331) {
		MSG("username error:%s",username);
		goto out;
	}

	snprintf(sendbuf,MAXLINE,"%s %s\r\n",CLI_PASS,password);
	ret = cmd_to_server(sockfd,sendbuf);
	if(ret == -1) {
		MSG("cmd_to_server failed:%s %s",CLI_PASS,password);
	}

	ret = res_from_server(sockfd,NULL,0);
	if(ret == -1) {
		MSG("res_from_server failed");
		goto out;
	}
	else if(ret != 230) {
		MSG("username or password error,not logged in");
		goto out;
	}
	
	run_log("login succeeded - %s %s %s %s",ip_addr,port,username,password);
	ret = close(sockfd);
	if(ret == -1) {
		MSG("close failed:scan control socket");
	}
	return 0;


out:
	run_log("login failed - %s %s %s %s",ip_addr,port,username,password);
	ret = close(sockfd);
	if(ret == -1) {
		MSG("close failed:scan control socket");
	}
	return -1;
}

static void *scan_thread(void *arg) {

	struct scan_thread_arg *parg = (struct scan_thread_arg *)arg;
	unsigned int i;
	struct in_addr tmp_addr;
	int ret;
	char buf[MAXLINE];

	for(i = 0; i < parg->naddr; i++) {
		tmp_addr.s_addr = htonl(parg->from_addr+i);
		//printf("%s\n",inet_ntoa(tmp_addr));
		ret = try_login(inet_ntoa(tmp_addr),SCAN_PORT,
					SCAN_USERNAME,SCAN_PASSWORD);
		if(ret == 0) {
			if(pthread_mutex_lock(&scan_mutex) != 0) {
				MSG("pthread_mutex_lock failed:scan_mutex");
				pthread_exit(NULL);
			}
			snprintf(buf,MAXLINE,"%s\t%s\t%s\t%s\n",inet_ntoa(tmp_addr),SCAN_PORT,
								SCAN_USERNAME,SCAN_PASSWORD);
			ret = writen(parg->fd_sites,buf,strlen(buf));
			if(ret == -1) {
				pthread_mutex_unlock(&scan_mutex);
				MSG("writen failed:%s to sites",buf);
				pthread_exit(NULL);
			}
			if(pthread_mutex_unlock(&scan_mutex) != 0) {
				MSG("pthread_mutex_unlock failed:scan_mutex");
				pthread_exit(NULL);
			}
		}
		#if 1
		if(pthread_mutex_lock(&scan_mutex) != 0) {
			MSG("pthread_mutex_lock failed:scan_mutex");
			pthread_exit(NULL);
		}
		printf("scanned_count = %d\n",++scanned_count);
		if(pthread_mutex_unlock(&scan_mutex) != 0) {
			MSG("pthread_mutex_unlock failed:scan_mutex");
			pthread_exit(NULL);
		}	
		#endif
	}


	pthread_exit(NULL);

}
